#pragma once

// __WFILE__
#define WIDEN2(x) L##x
#define WIDEN(x) WIDEN2(x)
#define __WFILE__ WIDEN(__FILE__)

// C++ headers
#include <iomanip>
#include <ostream>
#include <string>

// Windows headers
#include <Windows.h>

// Direct3D headers
#include <d3d11.h>
#include <DirectXMath.h>

// Tracing
#define HR_TRACE(hr, str) D3DApp::LogTraceMessage(__WFILE__, __LINE__, hr, str)

// COM helper
#define ReleaseCOM(x) if (x) { x->Release(); x = NULL; }

//------------------------------------------------------------------------------
// This class encapsulates a generic Direct3D application
//------------------------------------------------------------------------------
class D3DApp
{
	// Constants
	static const std::wstring WINDOW_CLASS_NAME;
	static const int MIN_WINDOW_WIDTH = 200;
	static const int MIN_WINDOW_HEIGHT = 200;

	// Fields
	ID3D11Texture2D *d3dDepthStencilBuffer;
	ID3D11DepthStencilView *d3dDepthStencilView;
	ID3D11Device *d3dDevice;
	ID3D11DeviceContext *d3dDeviceContext;
	ID3D11RenderTargetView *d3dRenderTargetView;
	float deltaTime;
	IDXGISwapChain *dxgiSwapChain;
	static std::wstring fatalMessage;
	const HINSTANCE hInstance;
	HICON hWndIcon;
	HCURSOR hWndCursor;
	HWND hWnd;
	bool paused;
	int returnCode;
	bool runCalled;
	__int64 timerLastTick;
	__int64 timerPauseTick;
	float timerPeriod;
	std::wstring windowCaption;
	int windowHeight;
	int windowWidth;
	HRESULT wndProcErrorCode;

	// Internal functions
	HRESULT CreateRenderTargets();
	static void DisplayErrorMessage();
	HRESULT DrawFrame();
	HRESULT Initialize();
	static LONG WINAPI LogUnhandledException(LPEXCEPTION_POINTERS lpExceptionPointers);
	void Pause();
	LRESULT ProcessWindowMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
	HRESULT Resize();
	int Run2();
	void Unpause();
	static LRESULT CALLBACK WindowProcedure(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

	// No copying / assignment
	D3DApp(const D3DApp &);
	D3DApp & operator=(const D3DApp &);

protected:
	// Event handlers
	virtual void OnDrawFrame() { }
	virtual HRESULT OnInitialize() { return S_OK; }
	virtual HRESULT OnLeftMouseDown(int x, int y) { return S_OK; }
	virtual HRESULT OnLeftMouseUp(int x, int y) { return S_OK; }
	virtual HRESULT OnMouseMove(int x, int y) { return S_OK; }
	virtual HRESULT OnMouseWheel(int x, int y, float delta) { return S_OK; }
	virtual HRESULT OnResize() { return S_OK; }
	virtual HRESULT OnRightMouseDown(int x, int y) { return S_OK; }
	virtual HRESULT OnRightMouseUp(int x, int y) { return S_OK; }

public:

	// Constructors/destructor
	D3DApp();
	virtual ~D3DApp();

	// Methods
	static std::wostream & LogMessage();
	static HRESULT LogTraceMessage(const std::wstring &file, int line, HRESULT hr, const std::wstring &str);
	int Run();

	// Getters
	ID3D11Device *D3DDevice() const { return d3dDevice; }
	ID3D11DeviceContext *D3DDeviceContext() const { return d3dDeviceContext; }
	float DeltaTime() const { return deltaTime; }
	ID3D11DepthStencilView *DepthStencilView() const { return d3dDepthStencilView; }
	static const std::wstring & FatalMessage() { return fatalMessage; }
	HINSTANCE InstanceHandle() const { return hInstance; }
	ID3D11RenderTargetView *RenderTargetView() const { return d3dRenderTargetView; }
	int ReturnCode() const { return returnCode; }
	const std::wstring & WindowCaption() const { return windowCaption; }
	HCURSOR WindowCursor() const { return hWndCursor; }
	HWND WindowHandle() const { return hWnd; }
	int WindowHeight() const { return windowHeight; }
	HICON WindowIcon() const { return hWndIcon; }
	int WindowWidth() const { return windowWidth; }

	// Setters
	static void SetFatalMessage(const std::wstring &fatalMessage) { D3DApp::fatalMessage = fatalMessage; }
	void SetWindowCaption(const std::wstring &windowCaption) { this->windowCaption = windowCaption; }
	void SetWindowCursor(HCURSOR hWndCursor) { this->hWndCursor = hWndCursor; }
	void SetWindowHeight(int windowHeight) { this->windowHeight = windowHeight; }
	void SetWindowIcon(HICON hWndIcon) { this->hWndIcon = hWndIcon; }
	void SetWindowWidth(int windowWidth) { this->windowWidth = windowWidth; }
};